﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Graphics.PackedVector;

namespace ProceduralGolf
{
    class TexturePerlinNoiser : TextureEffect
    {
        private GraphicsDevice graphics;
        private Effect perlinNoiseEffect;

        Texture2D permTexture;
        Texture2D permTexture2d;
        Texture2D permGradTexture;
        Texture2D gradTexture4d;

        public TexturePerlinNoiser(Game game)
        {
            this.graphics = game.GraphicsDevice;
            perlinNoiseEffect = game.Content.Load<Effect>("perlin");
            spriteBatch = new SpriteBatch(graphics);
            GenerateTextures();
            perlinNoiseEffect.Parameters["permTexture"].SetValue(permTexture);
            perlinNoiseEffect.Parameters["permTexture2d"].SetValue(permTexture2d);
            perlinNoiseEffect.Parameters["permGradTexture"].SetValue(permGradTexture);
            perlinNoiseEffect.Parameters["gradTexture4d"].SetValue(gradTexture4d);
            perlinNoiseEffect.Parameters["offset"].SetValue((int)(new Random().NextDouble() * 1000));
        }

        private float scaleFreq(float freq, Vector2 terrain)
        {
            Vector2 reference = new Vector2(512, 512);
            float reference_size = reference.Length();
            float terrain_size = terrain.Length();

            return freq*terrain_size/reference_size;
        }

        public Texture2D noise(int width, int height)
        {
            return noise(width, height, 3, 2.0f, 0.5f, 5.0f, 0.5f);
        }

        public Texture2D noise(int width, int height, int octaves, float lacunarity, float gain, float freq, float amp)
        {
            GraphicsMutex.GetMutex().WaitOne();

            Texture2D result = new Texture2D(graphics, width, height, 1, TextureUsage.Linear, graphics.PresentationParameters.BackBufferFormat);
            
            freq = scaleFreq(freq, new Vector2(width, height));

            RenderTarget2D target = new RenderTarget2D(graphics, result.Width, result.Height, 1, graphics.PresentationParameters.BackBufferFormat);
            DepthStencilBuffer stencilBuffer = new DepthStencilBuffer(graphics, result.Width, result.Height, graphics.DepthStencilBuffer.Format);
            DepthStencilBuffer oldBuffer = graphics.DepthStencilBuffer;

            graphics.DepthStencilBuffer = stencilBuffer;
            graphics.SetRenderTarget(0, target);
            graphics.Clear(Color.Black);

            perlinNoiseEffect.Begin();
            perlinNoiseEffect.Parameters["octaves"].SetValue(octaves);
            perlinNoiseEffect.Parameters["lacunarity"].SetValue(lacunarity);
            perlinNoiseEffect.Parameters["gain"].SetValue(gain);
            perlinNoiseEffect.Parameters["freq"].SetValue(freq);
            perlinNoiseEffect.Parameters["amp"].SetValue(amp);

            foreach (EffectPass pass in perlinNoiseEffect.CurrentTechnique.Passes)
            {
                spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);
                pass.Begin();

                spriteBatch.Draw(permTexture2d, new Rectangle(0, 0, result.Width, result.Height), Color.White);
                pass.End();
                spriteBatch.End();
            }

            perlinNoiseEffect.End();

            graphics.SetRenderTarget(0, null);
            graphics.DepthStencilBuffer = oldBuffer;

            result = target.GetTexture();
            stencilBuffer.Dispose();
            target.Dispose();

            GraphicsMutex.GetMutex().ReleaseMutex();

            return result;

        }

        //public Texture2D noise(int width, int height, int octaves, float lacunarity, float gain, float freq, float amp)
        //{
        //    Texture2D result = new Texture2D(graphics, width, height, 1, TextureUsage.Linear, graphics.PresentationParameters.BackBufferFormat);
        //    RenderTarget2D target = new RenderTarget2D(graphics, result.Width, result.Height, 1, graphics.PresentationParameters.BackBufferFormat);

        //    graphics.SetRenderTarget(0, target);

        //    perlinNoiseEffect.Begin();
        //    perlinNoiseEffect.Parameters["octaves"].SetValue(octaves);
        //    perlinNoiseEffect.Parameters["lacunarity"].SetValue(lacunarity);
        //    perlinNoiseEffect.Parameters["gain"].SetValue(gain);
        //    perlinNoiseEffect.Parameters["freq"].SetValue(freq);
        //    perlinNoiseEffect.Parameters["amp"].SetValue(amp);

        //    foreach (EffectPass pass in perlinNoiseEffect.CurrentTechnique.Passes)
        //    {
        //        spriteBatch.Begin(SpriteBlendMode.Additive, SpriteSortMode.Immediate, SaveStateMode.None);
        //        pass.Begin();

        //        spriteBatch.Draw(permTexture2d, new Rectangle(0, 0, result.Width, result.Height), Color.White);
        //        pass.End();
        //        spriteBatch.End();
        //    }

        //    perlinNoiseEffect.End();

        //    graphics.SetRenderTarget(0, null);
        //    result = target.GetTexture();
        //    target.Dispose();

        //    return result;

        //}


                // gradients for 3d noise
        static float[,] g3 =  
        {
            {1,1,0},
            {-1,1,0},
            {1,-1,0},
            {-1,-1,0},
            {1,0,1},
            {-1,0,1},
            {1,0,-1},
            {-1,0,-1}, 
            {0,1,1},
            {0,-1,1},
            {0,1,-1},
            {0,-1,-1},
            {1,1,0},
            {0,-1,1},
            {-1,1,0},
            {0,-1,-1}
        };

        // gradients for 4D noise
        static float[,] g4 = 
        {
	        {0, -1, -1, -1},
	        {0, -1, -1, 1},
	        {0, -1, 1, -1},
	        {0, -1, 1, 1},
	        {0, 1, -1, -1},
	        {0, 1, -1, 1},
	        {0, 1, 1, -1},
	        {0, 1, 1, 1},
	        {-1, -1, 0, -1},
	        {-1, 1, 0, -1},
	        {1, -1, 0, -1},
	        {1, 1, 0, -1},
	        {-1, -1, 0, 1},
	        {-1, 1, 0, 1},
	        {1, -1, 0, 1},
	        {1, 1, 0, 1},
        	
	        {-1, 0, -1, -1},
	        {1, 0, -1, -1},
	        {-1, 0, -1, 1},
	        {1, 0, -1, 1},
	        {-1, 0, 1, -1},
	        {1, 0, 1, -1},
	        {-1, 0, 1, 1},
	        {1, 0, 1, 1},
	        {0, -1, -1, 0},
	        {0, -1, -1, 0},
	        {0, -1, 1, 0},
	        {0, -1, 1, 0},
	        {0, 1, -1, 0},
	        {0, 1, -1, 0},
	        {0, 1, 1, 0},
	        {0, 1, 1, 0}
        };

        static int[] perm = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95,
			    96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37,
			    240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62,
			    94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56,
			    87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139,
			    48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133,
			    230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25,
			    63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200,
			    196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3,
			    64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255,
			    82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42,
			    223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153,
			    101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79,
			    113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242,
			    193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249,
			    14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204,
			    176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222,
			    114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 };
 

        private void GeneratePermTexture()
        {
            permTexture = new Texture2D(graphics, 256, 1, 1, TextureUsage.None, SurfaceFormat.Luminance8);
            byte[] data = new byte[256 * 1];
            for (int x = 0; x < 256; x++)
            {
                for (int y = 0; y < 1; y++)
                {
                    data[x + (y * 256)] = (byte)(perm[x]);
                }
            }
            permTexture.SetData<byte>(data);
        }

        int perm2d(int i)
        {
            return perm[i % 256];
        }

        private void GeneratePermTexture2d()
        {
            permTexture2d = new Texture2D(graphics, 256, 256, 1, TextureUsage.None, SurfaceFormat.Color);
            Color[] data = new Color[256 * 256];
            for (int x = 0; x < 256; x++)
            {
                for (int y = 0; y < 256; y++)
                {
                    int A = perm2d(x) + y;
                    int AA = perm2d(A);
                    int AB = perm2d(A + 1);
                    int B = perm2d(x + 1) + y;
                    int BA = perm2d(B);
                    int BB = perm2d(B + 1);
                    data[x + (y * 256)] = new Color((byte)(AA), (byte)(AB),
                                                    (byte)(BA), (byte)(BB));
                }
            }
            permTexture2d.SetData<Color>(data);
        }

        private void GeneratePermGradTexture()
        {
            permGradTexture = new Texture2D(graphics, 256, 1, 1, TextureUsage.None, SurfaceFormat.NormalizedByte4);
            NormalizedByte4[] data = new NormalizedByte4[256 * 1];
            for (int x = 0; x < 256; x++)
            {
                for (int y = 0; y < 1; y++)
                {
                    data[x + (y * 256)] = new NormalizedByte4(g3[perm[x] % 16, 0], g3[perm[x] % 16, 1], g3[perm[x] % 16, 2], 1);
                }
            }
            permGradTexture.SetData<NormalizedByte4>(data);
        }

        private void GenerateGradTexture4d()
        {
            gradTexture4d = new Texture2D(graphics, 32, 1, 1, TextureUsage.None, SurfaceFormat.NormalizedByte4);
            NormalizedByte4[] data = new NormalizedByte4[32 * 1];
            for (int x = 0; x < 32; x++)
            {
                for (int y = 0; y < 1; y++)
                {
                    data[x + (y * 32)] = new NormalizedByte4(g4[x, 0], g4[x, 1], g4[x, 2], g4[x, 3]);
                }
            }
            gradTexture4d.SetData<NormalizedByte4>(data);
        }

        /// <summary>
        /// Generates all of the needed textures on the CPU
        /// </summary>
        private void GenerateTextures()
        {
            GeneratePermTexture();
            GeneratePermTexture2d();
            GeneratePermGradTexture();
            GenerateGradTexture4d();
        }
    }
}
